/*
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|  
 * |#       #|  Copyright (c) 1999-2006 CardContact Software & System Consulting
 * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
 *  --------- 
 *
 *  This file is part of OpenSCDP.
 *
 *  OpenSCDP is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  OpenSCDP is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenSCDP; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.jl.process.util;

import java.io.UnsupportedEncodingException;

/**
 * Implements a simple Base64 encoder and decoder as defined in RFC 4648
 * 
 * @author Andreas Schwier (info@cardcontact.de)
 */
public class Base64 {

	private static int base64todec(char c) {
		int v;

		if ((c >= 'A') && (c <= 'Z'))
			v = c - 'A';
		else if ((c >= 'a') && (c <= 'z'))
			v = c - 'a' + 26;
		else if ((c >= '0') && (c <= '9'))
			v = c - '0' + 52;
		else if (c == '/')
			v = 63;
		else if (c == '+')
			v = 62;
		else if (c == '=')
			v = 64;
		else
			v = -1;

		return v;
	}



	private static char dectobase64(int v) {
		char c;

		if (v == 64)
			c = '=';
		else if (v == 63)
			c = '/';
		else if (v == 62)
			c = '+';
		else if ((v >= 52) && (v <= 61))
			c = (char)(v - 52 + '0');
		else if ((v >= 26) && (v <= 51))
			c = (char)(v - 26 + 'a');
		else
			c = (char)(v + 'A');

		return c;

	}



	/**
	 * Convert Base64 encoded string into byte array
	 * 
	 * @param str Base64 String that may contain special characters
	 * @return Byte array with decoded data
	 */
	public static byte[] decode(String str) throws UnsupportedEncodingException {

		ByteBuffer bb = new ByteBuffer(str.length() / 2);
		int padcnt = 0;
		int i = 0;
		int l = str.length();
		
		while (i < l) {
			int digit = base64todec(str.charAt(i));
			
			if ((digit < 0) || (digit > 63)) {		// Skip non Base64 character
				i++;
				continue;
			}
			
			int shifter = 18;						// Shift first digit 18 bits to the left
			int akku = 0;
			while ((i < l) && (shifter >= 0)) {		// Collect a quantum
				digit = base64todec(str.charAt(i));
				if (digit < 0) {
					throw new UnsupportedEncodingException("Invalid character " + str.charAt(i) + " in Base64 string");
				}
				
				if (digit < 64) {
					if (padcnt > 0) {
						throw new UnsupportedEncodingException("Invalid padding character in Base64 string");
					}
					akku |= digit << shifter;
				} else {
					padcnt++;
				}
				shifter -= 6;
				i++;
			}
			
			if (shifter < 18) {			// Some Base64 decoded ?
				while (shifter >= 0) {	// Compensate for missing padding characters
					padcnt++;
					shifter -= 6;
				}
				bb.append((byte)((akku >> 16) & 0xFF));
				if (padcnt < 2) {
					bb.append((byte)((akku >> 8) & 0xFF));
					if (padcnt < 1)
						bb.append((byte)( akku & 0xFF));
				}
			}
		}

		return bb.getBytes();
	}



	/**
	 * Convert byte array to Base64 encoded string
	 * 
	 * @param bin Byte array
	 * @param linebreak true to include line breaks
	 * @return String with encoded data
	 */
	public static String encode(byte[] bin, boolean linebreak) {

		int i = 0;
		int l = bin.length;
		int a0,a1,a2,a3;

		StringBuffer sb = new StringBuffer(bin.length);

		while(l > 0) {
			a0 = (bin[i] >> 2) & 0x3F;
			a1 = (bin[i] << 4) & 0x30;
			a2 = 0x40;
			a3 = 0x40;
			l--;
			i++;

			if (l > 0) {
				a1 |= (bin[i] >> 4) & 0x0F;
				a2  = (bin[i] << 2) & 0x3C;
				l--;
				i++;

				if (l > 0) {
					a2 |= (bin[i] >> 6) & 0x03;
					a3  =  bin[i]       & 0x3F;
					l--;
					i++;
				}
			}
			sb.append(dectobase64(a0));
			sb.append(dectobase64(a1));
			sb.append(dectobase64(a2));
			sb.append(dectobase64(a3));
			if (linebreak && ((i % 48) == 0))
				sb.append('\n');
		}

		return sb.toString();
	}



	/**
	 * Convert byte array to Base64 encoded string
	 * 
	 * @param bin Byte array
	 * @return String with encoded data
	 */
	public static String encode(byte[] bin) {
		return encode(bin, false);
	}



	/**
	 * Convert byte array to Base64 encoded string
	 * 
	 * @param bin Byte array
	 * @return String with encoded data
	 */
	public static String encodeWithLineBreaks(byte[] bin) {
		return encode(bin, true);
	}
}
